package com.sudoku.solver;

import java.util.ArrayList;
import java.util.HashMap;
import java.util.Iterator;
import java.util.Map.Entry;

import com.sudoku.board.Board;
import com.sudoku.board.Tile;
import com.sudoku.utility.Pair;

public class HiddenTwin extends Strategy {

	public HiddenTwin(Solver solver) {
		super(solver);
	}
	
	@Override
	public boolean solve(Board board) {
		stuck=true;
		/*
		 * Find hidden twins. 2 pairs of numbers that MUST go in exactly those 2 tiles. We can eliminate the other penciled numbers in those 2 tiles
		 */
		// rows
		for (int i = 0; i < 9; i++) {
			if (hiddenTwin(board.getTilesInRow(i))) {
				stuck = false;
			}
		}
		// cols
		for (int i = 0; i < 9; i++) {
			if (hiddenTwin(board.getTilesInCol(i))) {
				stuck = false;
			}
		}
		// box
		for (int i = 0; i < 9; i++) {
			if (hiddenTwin(board.getData().get(i).getData())) {
				stuck = false;
			}
		}
		
		return stuck;
	}

	/**
	 * Find hidden twins. 2 pairs of numbers that MUST go in exactly those 2 tiles. We can eliminate the other penciled numbers in those 2 tiles
	 * 
	 * @param tile
	 * @return if change to tile was made
	 */
	public boolean hiddenTwin(final ArrayList<Tile> tiles) {
		HashMap<ArrayList<Pair<Integer, Integer>>, Tile> pairs = new HashMap<ArrayList<Pair<Integer, Integer>>, Tile>();
		boolean foundPair = false;

		// make pairs for each tile
		for (Tile tile : tiles) {
			if (tile.isBlank()) {
				ArrayList<Pair<Integer, Integer>> tilePairs = new ArrayList<Pair<Integer, Integer>>();
				for (int i = 0; i < tile.getPencilNumbers().size(); i++) {
					for (int j = i + 1; j < tile.getPencilNumbers().size(); j++) {
						Pair<Integer, Integer> p = new Pair<Integer, Integer>(tile.getPencilNumbers().get(i), tile.getPencilNumbers().get(j));
						tilePairs.add(p);
					}
				}
				pairs.put(tilePairs, tile);
			}
		}
		// find pairs between 2 tiles
		for (Iterator<Entry<ArrayList<Pair<Integer, Integer>>, Tile>> first = pairs.entrySet().iterator(); first.hasNext();) {

			Entry<ArrayList<Pair<Integer, Integer>>, Tile> firstSet = first.next();
			ArrayList<Pair<Integer, Integer>> firstTilePairs = firstSet.getKey();

			for (Iterator<Entry<ArrayList<Pair<Integer, Integer>>, Tile>> second = pairs.entrySet().iterator(); second.hasNext();) {

				Entry<ArrayList<Pair<Integer, Integer>>, Tile> secondSet = second.next();
				ArrayList<Pair<Integer, Integer>> secondTilePairs = secondSet.getKey();
				// skip if tiles are the same
				if (!firstSet.equals(secondSet)) {
					for (int i = 0; i < firstTilePairs.size(); i++) {
						for (int j = 0; j < secondTilePairs.size(); j++) {
							if (firstTilePairs.get(i).equals(secondTilePairs.get(j))) {
								Tile firstTile = firstSet.getValue();
								Tile secondTile = secondSet.getValue();
								int firstNumber = firstTilePairs.get(i).getFirst();
								int secondNumber = firstTilePairs.get(i).getSecond();
								boolean unique = true;
								// now go through rest of tiles to make sure these two numbers aren't somewhere else
								for (Tile tile : tiles) {
									if (tile.isBlank() && !tile.equals(firstTile) && !tile.equals(secondTile)) {
										if (tile.getPencilNumbers().contains(firstNumber) || tile.getPencilNumbers().contains(secondNumber)) {
											unique = false;
											break;
										}
									}
								}
								if (unique) {
									if (firstTile.getPencilNumbers().size() > 2) {
										// succeeded at finding a hidden pair, only put the hidden pair in these tiles
										firstTile.clearPencilNumbers();
										firstTile.addPencilNumber(firstTilePairs.get(i).getFirst());
										firstTile.addPencilNumber(firstTilePairs.get(i).getSecond());
										foundPair = true;
										System.out.println("Hidden Twin - update at " + firstTile.getCoords() + " from box " + firstTile.getBox().getCoords()
												+ " with data " + firstTile.getPencilNumbers().get(0) + " " + firstTile.getPencilNumbers().get(1));
									}

									if (secondTile.getPencilNumbers().size() > 2) {
										secondTile.clearPencilNumbers();
										secondTile.addPencilNumber(secondTilePairs.get(j).getFirst());
										secondTile.addPencilNumber(secondTilePairs.get(j).getSecond());
										foundPair = true;
										System.out.println("Hidden Twin - update at " + secondTile.getCoords() + " from box " + secondTile.getBox().getCoords()
												+ " with data " + secondTile.getPencilNumbers().get(0) + " " + secondTile.getPencilNumbers().get(1));
									}

									// these two tiles are now a naked pair - remove all other penciled numbers from tiles these two affect
									if (foundPair) {
										nakedPair(tiles, firstTile, secondTile);
									}
								}
							}
						}
					}
				}
			}
		}
		return foundPair;
	}

	/**
	 * Removed the penciled numbers of this pair of tiles from set of tiles passed in.
	 * Assumed the pair of tiles passed in have only two penciled numbers 
	 * @param tiles
	 * @param firstTile
	 * @param secondTile
	 */
	private void nakedPair(final ArrayList<Tile> tiles, Tile firstTile, Tile secondTile) {
		int first=firstTile.getPencilNumbers().get(0);
		int second=firstTile.getPencilNumbers().get(1);
		for(Tile tile: tiles){
			if(tile.isBlank() && !tile.equals(firstTile) && !tile.equals(secondTile)){
				tile.removePencilNumber(first);
				tile.removePencilNumber(second);
				System.out.println("Naked Pair - removed "+first+" "+second+" from "+tile.getCoords()+ " from box "+tile.getBox().getCoords());
			}
		}
		
	}

}
